﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Transactions;

using Common.Logging;
using Microsoft.Practices.ServiceLocation;

using Happy.ExtentionMethods;

namespace Happy.Event.Internal
{
    internal sealed class DefaultEventPublisher : IEventPublisher
    {
        private readonly Dictionary<Type, object> _services = new Dictionary<Type, object>();

        public void Publish<TEvent>(TEvent @event)
        {
            @event.MustNotNull("event");

            var context = this.CreateEventPublishContext(@event);

            using (var ts = new TransactionScope(TransactionScopeOption.Required))
            {
                context.ExecuteNext();

                ts.Complete();
            }
        }

        public IEventPublisher AddService<T>(T service)
        {
            _services[typeof(T)] = service;

            return this;
        }

        public T GetService<T>()
        {
            return (T)_services[typeof(T)];
        }

        private EventPublishContext CreateEventPublishContext<TEvent>(TEvent @event)
        {
            return new EventPublishContext(this, @event, () =>
            {
                this.ExecuteSyncEventSubscribers(@event);

                this.ExecuteAsyncEventSubscribers(@event);
            });
        }

        private void ExecuteSyncEventSubscribers<TEvent>(TEvent @event)
        {
            var syncEventSubscribers = this.GetSyncEventSubscribers<TEvent>();

            foreach (var syncEventSubscriber in syncEventSubscribers)
            {
                syncEventSubscriber.Handle(@event);
            }
        }

        private void ExecuteAsyncEventSubscribers<TEvent>(TEvent @event)
        {
            var asyncEventSubscribers = this.GetAsyncEventSubscribers<TEvent>();

            ThreadPool.QueueUserWorkItem((state) =>
            {
                foreach (var asyncEventSubscriber in asyncEventSubscribers)
                {
                    try
                    {
                        asyncEventSubscriber.Handle(@event);
                    }
                    catch (Exception ex)
                    {
                        string message = string.Format(
                            Resource.Messages.Error_AsyncEventSubscriberExecuteFailure,
                            asyncEventSubscriber.GetType().FullName);

                        LogManager.GetCurrentClassLogger().Fatal(message, ex);
                    }
                }
            });
        }

        private IEnumerable<ISyncEventSubscriber<TEvent>> GetSyncEventSubscribers<TEvent>()
        {
            return ServiceLocator
                .Current
                .GetAllInstances<ISyncEventSubscriber<TEvent>>();
        }

        private IEnumerable<IAsyncEventSubscriber<TEvent>> GetAsyncEventSubscribers<TEvent>()
        {
            return ServiceLocator
                .Current
                .GetAllInstances<IAsyncEventSubscriber<TEvent>>();
        }
    }
}
